/***********************************************************************************
Copy right:	    Coffee Tech.
Author:         jiaoyue
Date:           2019-08-06
Description:    msg_queue模块:提供消息队列组件
***********************************************************************************/

#include <msg_queue.h>

//消息通用头
struct msg_head
{
    long msg_type;  //命令类型
    key_t key;  //客户端的key
};

#define MSG_PATH "/tmp/ipc/msgqueue/"

#define MAGIC_ID 'j'

#define LEN_MSG_HEAD sizeof(struct msg_head)
#define MSG_DATA(msgh)  ((void*)(((char*)msgh) + LEN_MSG_HEAD))
#define MSG_DATA_LEN(len) (len - LEN_MSG_HEAD)

/***************************子系统使用********************************/
/**
 * @brief 初始化服务消息队列
 * @param msg 传入参数
 * @param sname 服务名称
 * @return 0 -1
 */
int msg_service_init(struct msg_param *msg, const char *sname)
{
    assert(NULL != msg);
    assert(NULL != sname && strlen(sname) > 0);
    key_t key;
    int ret;
    int service_id;
    char sys_cmd[256];
    char spath[256];

    sprintf(spath, "/tmp/ipc/msgqueue/%s-s", sname);
    sprintf(sys_cmd, "%s %s", "touch", spath);
    ret = system(sys_cmd);
    UNUSED(ret);

    //创建key
    key = ftok(spath, MAGIC_ID);
    if(key < 0){
        perror("fail to ftok");
        printf("error :spath = %s\n", spath);
        return -1;
    }

    //创建消息队列
    service_id = msgget(key, IPC_CREAT|0666);
    if (service_id < 0)
    {
        perror("fail to msgget");
        return -1;
    }

    msg->skey = key;
    strcpy(msg->sname, sname);

    return 0;
}

/**
 * @brief 服务接收自己消息队列消息
 * @param msg 通信所需结构体
 * @param msg_ptr 有效信息
 * @param msgsz 有效信息长度
 * @return 错误-1 成功返回接收字节数
 */
int msg_service_recv(struct msg_param *msg, void *msg_ptr, size_t msgsz)
{
    assert(NULL != msg);
    assert(NULL != msg_ptr);
    int nbytes;
    void *all_msg;

    int service_id = msgget(msg->skey, 0666);
    if (service_id < 0)
    {
        perror("fail to msgget");
        return -1;
    }

    int total_len = LEN_MSG_HEAD + msgsz;
    all_msg = malloc(total_len);
    if(NULL == all_msg)
    {
        perror("fail to malloc");
        return -1;
    }
    memset(all_msg, 0, total_len);

    nbytes = msgrcv(service_id, all_msg, total_len - sizeof(long), 0, 0);  //非阻塞取第一个消息
    if(nbytes < 0)
    {
        perror("msgrcv error");
        free(all_msg);
        return -1;
    }

    //结构体对齐问题处理
    nbytes = nbytes + sizeof(long) - LEN_MSG_HEAD;

    struct msg_head *msgh = (struct msg_head *)all_msg;
    msg->msg_type = msgh->msg_type;  //保存消息类型
    msg->ckey = msgh->key;  //保存对方的key值

    memcpy(msg_ptr, MSG_DATA(msgh), nbytes);  //这里nbytes不包含mtype
    free(all_msg);

    return nbytes;
}

/**
 * @brief 服务往外回复消息
 * @param msg 通信所需结构体
 * @param msg_ptr 有效信息
 * @param msgsz 有效信息长度
 * @return 0 -1
 */
int msg_service_send(const struct msg_param *msg, void *msg_ptr, size_t msgsz)
{
    assert(NULL != msg);
    assert(NULL != msg_ptr);
    int cli_id = msgget(msg->ckey, 0666);
    if(cli_id < 0)
    {
        perror("fail to msgget");
        return -1;
    }

    int total_len = LEN_MSG_HEAD + msgsz;
    struct msg_head *all_msg = (struct msg_head *)malloc(total_len);
    if(NULL == all_msg)
    {
        perror("fail to malloc");
        return -1;
    }
    memset(all_msg, 0, total_len);
    all_msg->msg_type = msg->msg_type;  //填充消息类型
    all_msg->key = msg->skey;  //这个不加也行，客户端并不关心
    memcpy(MSG_DATA(all_msg), msg_ptr, msgsz);  //增加消息体

    int ret = msgsnd(cli_id, all_msg, total_len - sizeof(long), 0);
    if(ret < 0)
    {
        free(all_msg);
        return -1;
    }
    else
    {
        free(all_msg);
        return 0;
    }
}

/***************************和服务通信使用********************************/
/**
 * @brief 应用程序消息队列初始化
 * @param msg 传入参数
 * @param cname 客户端名称
 * @param sname 服务端名称
 * @return 0 -1
 */
int msg_client_init(struct msg_param *msg, const char *cname, const char *sname)
{
    assert(NULL != msg);
    assert(NULL != cname && strlen(cname) > 0);
    assert(NULL != sname && strlen(sname) > 0);
    key_t ckey, skey;
    int client_id;

    //创建相应文件路径
    char sys_cmd[256];
    char cpath[256], spath[256];
    sprintf(cpath, "/tmp/ipc/msgqueue/%s-c-%s", sname, cname);
    sprintf(sys_cmd, "%s %s", "touch", cpath);
    int ret = system(sys_cmd);
    UNUSED(ret);

    //处理客户端key
    ckey = ftok(cpath, MAGIC_ID);
    if(ckey < 0){
        perror("fail to ftok");
        return -1;
    }

    msg->ckey = ckey;
    strcpy(msg->cname, cname);

    //处理服务器key
    sprintf(spath, "/tmp/ipc/msgqueue/%s-s", sname);
    skey = ftok(spath, MAGIC_ID);
    if(skey < 0){
        perror("fail to ftok");
        printf("error :spath = %s\n", spath);
        return -1;
    }

    //创建消息队列
    client_id = msgget(ckey, IPC_CREAT|0666);
    if (client_id < 0)
    {
        perror("fail to msgget");
        return -1;
    }

    msg->skey = skey;
    strcpy(msg->sname, sname);

    return 0;
}

/**
 * @brief 往某个服务发送消息
 * @param msg 通信所需结构体
 * @param msg_ptr 有效信息
 * @param msgsz 有效信息长度
 * @return 错误-1 成功返回发送字节数
 */
int msg_client_send(const struct msg_param *msg, void *msg_ptr, size_t msgsz)
{
    assert(NULL != msg);
    assert(NULL != msg_ptr);
    int ret;
    int service_id = msgget(msg->skey, 0666);
    if (service_id < 0)
    {
        perror("fail to msgget");
        return -1;
    }

    int total_len = LEN_MSG_HEAD + msgsz;
    struct msg_head *all_msg = (struct msg_head *)malloc(total_len);
    if(NULL == all_msg)
    {
        perror("fail to malloc");
        return -1;
    }
    memset(all_msg, 0, total_len);
    all_msg->msg_type = msg->msg_type;  //增加消息类型
    all_msg->key = msg->ckey;  //增加客户端key
    memcpy(MSG_DATA(all_msg), msg_ptr, msgsz);  //增加消息体

    ret = msgsnd(service_id, all_msg, total_len - sizeof(long), 0);
    if(ret < 0)
    {
        perror("msgsnd to error ");
        free(all_msg);
        return -1;
    }
    else
    {
        free(all_msg);
        return ret;
    }
}

/**
 * @brief 从消息队列获取第一个消息，可控制阻塞或非阻塞
 * @param msg 通信所需结构体
 * @param msg_ptr 有效信息
 * @param msgsz 有效信息长度
 * @param wait  是否阻塞，0为非阻塞，非0为阻塞
 * @return 错误-1 成功返回接收字节数
 */
int msg_client_recv_with_flag(struct msg_param *msg, void *msg_ptr, size_t msgsz, int wait)
{
    assert(NULL != msg);
    assert(NULL != msg_ptr);
    int nbytes;
    void *all_msg;

    int cli_id = msgget(msg->ckey, 0666);
    if (cli_id < 0)
    {
        perror("fail to msgget");
        return -1;
    }

    int total_len = LEN_MSG_HEAD + msgsz;
    all_msg = malloc(total_len);
    if(NULL == all_msg)
    {
        perror("fail to malloc");
        return -1;
    }
    memset(all_msg, 0, total_len);

    if (wait)
    {
        nbytes = msgrcv(cli_id, all_msg, total_len - sizeof(long), 0, 0);  //阻塞取第一个消息
    }
    else
    {
        nbytes = msgrcv(cli_id, all_msg, total_len - sizeof(long), 0, IPC_NOWAIT);  //非阻塞取第一个消息
    }

    if(nbytes < 0)
    {
        perror("msgrcv error");
        free(all_msg);
        return -1;
    }

    //结构体对齐问题处理
    nbytes = nbytes + sizeof(long) - LEN_MSG_HEAD;

    struct msg_head *msgh = (struct msg_head *)all_msg;
    msg->msg_type = msgh->msg_type;  //保存消息类型
    //服务key可不处理

    memcpy(msg_ptr, MSG_DATA(msgh), nbytes);  //这里nbytes不包含mtype
    free(all_msg);

    return nbytes;
}

/**
 * @brief 从消息队列阻塞获取第一个消息
 * @param msg 通信所需结构体
 * @param msg_ptr 有效信息
 * @param msgsz 有效信息长度
 * @return 错误-1 成功返回接收字节数
 */
int msg_client_recv(struct msg_param *msg, void *msg_ptr, size_t msgsz)
{
    return msg_client_recv_with_flag(msg, msg_ptr, msgsz, 1);
}

/**
 * @brief 从消息队列非阻塞获取第一个消息
 * @param msg 通信所需结构体
 * @param msg_ptr 消息缓冲区，需要带消息类型
 * @param msgsz (实际长度，不用减去类型长度)
 * @return 错误-1 成功返回接收字节数
 */
int msg_client_recv_nowait(struct msg_param *msg, void *msg_ptr, size_t msgsz)
{
    return msg_client_recv_with_flag(msg, msg_ptr, msgsz, 0);
}
